<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            background: url(images/backimage.jpg) no-repeat;
            background-size: cover;
            overflow: hidden;
        }

        .main {
            margin: auto;
            position: relative;
        }

        .show {
            position: relative;
            border: 4px solid;
            margin: auto;
            border-image: linear-gradient(to right, #8f41e9, #578aef) 1;
        }



        .item {
            position: absolute;
            color: #f7f1f1;
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 1;
            transition: left .3s, top .3s, transform .1s;

        }

        .item img {
            width: 100%;
            height: 100%;
            border-radius: 7px;
        }

        /* 定义压着的图片 */
        .item:after {
            position: absolute;
            content: '';
            top: 0;
            bottom: 0;
            left: 0;
            right: 0;
            transition: background-color .2s;
        }

        /* 这个是为了   当配够  有disabled 属性的时候 就会透明掉 */
        .disabled:after {
            border-radius: 7px;
            background-color: rgba(0, 0, 0, 0.7);
        }
    </style>
</head>

<body>
    <div class="main"></div>
    <div class="show"></div>

</body>
<script>
    //导入图片 定义层级数 格式
    const imagesDate = [
        { img: './images/yingtao.png' },
        { img: './images/putao.png' },
        { img: './images/lizi.png' },
        { img: './images/菠萝.png' }]
    const size = 80//图片大小
    const rows = 6//行数
    const cols = 6//列数
    const delCount = 3//三个就消除
    const group = 4//一共有四组
    const cengCount = 5//层数有五层
    const cunArr = []//存储数据的数组
    const showDate = Array.from(new Array(delCount * group)).map(v => {
        return imagesDate.map(v => ({ ...v }))
    }).flat().sort(v => Math.random() - 0.5)// 算出图片总数然后用随机数打乱，flat把[[]]数组展开   随机0-0.5 缩小范围 sort() 方法对数组的项目进行排序。
    //1.利用多层for循环形成格式
    for (let cc = cengCount - 1; cc >= 0; cc--) {
        for (let i = 0; i < rows; i++) {
            for (let j = 0; j < cols; j++) {
                let pianyi = (cc + 1) % 2 === 0 ? size / 2 : 0 //设置层数偏移量，偶数层进行偏移
                //进行图片渲染
                let item = (Math.random() > 0.7 && showDate.pop()) //pop用完就删，当随机数大于0.7时，从renderData中取数组的最后一项，.pop的意思是取最后一项并从数组中移除该项
                item && cunArr.push(`<div class="item"   id="m${cc}-${i}-${j}"
                style="width:${size}px;height:${size}px;left:${size * j + pianyi}px;top:${size * i + pianyi}px;"><img src="${item.img}"/></div>`)
            }
        }

    }
    const main = document.querySelector('.main')
    const show = document.querySelector('.show')
    main.innerHTML = cunArr.reverse().join('')//reverse反转
    main.style.height = `${size * rows + size * 2}px`
    main.style.width = `${size * cols}px`
    show.style.height = `${size + 10}px`
    show.style.width = `${size * 6 + 10}px`
    //2.计算遮罩部分：在css定义一个class类，能标注暗色，判断是否需要添加class属性，利用classlist中的方法进行删除与添加class
    const checkDisabled = (items) => {
        (items || main.querySelectorAll('.item')).forEach((v, i) => {
            const arr = v.id.substring(1).split('-').map(v => Number(v))//把id取出来
            const isPy = (arr[0] + 1) % 2 === 0//还是偶数层偏移
            for (let i = arr[0] + 1; i <= cengCount - 1; i++) {//for循环，还是找
                const isPyB = (i + 1) % 2 === 0//所在的上一层处于第几层
                if (isPy === isPyB) {//身在不同层，但是坐标一样
                    const el = main.querySelector(`#m${i}-${arr[1]}-${arr[2]}`)//得到层数，后面依次为数组第二项，第三项
                    if (el) {//若是存在，则加遮盖
                        v.classList.add('disabled')
                        break;
                    }
                } else if (isPy && !isPyB) {//元素本身处于2，4，6  对比的处于1，3，5
                    if (![
                        `${i}-${arr[1]}-${arr[2]}`,
                        `${i}-${arr[1]}-${arr[2] + 1}`,
                        `${i}-${arr[1] + 1}-${arr[2]}`,
                        `${i}-${arr[1] + 1}-${arr[2] + 1}`//都为偏移量，就是周围的一圈图片
                    ].every(k => {//every一true全true 一false全false
                        return !main.querySelector('#m' + k) //k即为上述偏移量 （被遮住为false 加！）
                    })) {
                        v.classList.add('disabled')//如果为false 就加上disabled
                        break;
                    } else {
                        v.classList.remove('disabled')
                    }

                } else if (!isPy && isPyB) {//元素本身处于1，3，5  对比的处于2，4，6
                    if (![
                        `${i}-${arr[1]}-${arr[2]}`,
                        `${i}-${arr[1]}-${arr[2] - 1}`,
                        `${i}-${arr[1] - 1}-${arr[2]}`,
                        `${i}-${arr[1] - 1}-${arr[2] - 1}`
                    ].every(k => {
                        return !main.querySelector('#m' + k)
                    })) {
                        v.classList.add('disabled')
                        break;
                    } else {
                        v.classList.remove('disabled')
                    }

                }
            }
        })
    }
    let canMove = true
    //第三步   点击卡片进行消除计算
    let move = (me) => {
        //下边框的右边距离 和上边距离
        let left = show.offsetLeft - main.offsetLeft
        let top = show.offsetTop - main.offsetTop
        if (!canMove || me.className.indexOf('disabled') >= 0) {
            return   //禁用的不能点击移动
        }
        canMove = false
        if (show.children.length > 0) {
            let el = show.lastElementChild
            left = el.offsetLeft + size   //防止框内的元素重叠
        }
        me.style.top = `${top + 4}px`
        me.style.left = `${left + 1}px`
        me.transitionNamesCount = 0  //计数，有俩个动画，执行两次
        me.ontransitionend = (e) => {
            me.transitionNamesCount++
            if (me.transitionNamesCount === 2) {
                moveEnd(me)
                canMove = true
            }
        }
    }
    for (let i = 0; i < cunArr.length; i++) {
        const me = main.children[i]
        me.addEventListener('click', () => {
            move(me)
        })
    }
    //转移节点
    const moveEnd = (me) => {
        me.ontransitionend = null
        me.setAttribute('onclick', '')
        me.style.top = 0
        show.appendChild(me)  //这就把上面的节点转移到下边
        const findResult = [...show.children].filter(v => v.innerHTML === me.innerHTML)//...先转为数组 filter(函数) 方法创建一个新的数组，新数组中的元素是通过检查指定数组中符合条件的所有元素。
        if (findResult.length === 3) {
            findResult.forEach(v => {
                v.ontransitionend = () => {
                    show.removeChild(v); //动画完了以后 清除元素   但是  这样虽然清除了 元素  元素不能自动归位
                    [...show.children].forEach((v, i) => {//先转为数组再遍历
                        v.style.left = `${i * size + show.offsetLeft - main.offsetLeft}px`//归位 对齐
                    })
                }
                setTimeout(() =>  v.style.transform = 'scale(0)' )//动画消除
            })
        }
        setTimeout(() => {// setTimeout 多少秒后调用函数
            if (show.children.length > 6) {
                alert('小笨蛋，输了吧，重新开始吧')
                return location.reload()
            } else if (main.children.length === 0) {
                alert('恭喜你赢了')
                return location.reload()
            }
        }, 100)
        checkDisabled()
    }
    checkDisabled()
    for (let i = 0; i < cunArr.length; i++) {
        const me1 = main.children[i]
        me1.addEventListener('click', mksound)
    }
    function mksound() {
        // 当前频率
        var frequency = arrFrequency[start];
        // 如果到头，改变音调的变化规则（增减切换）
        if (!frequency) {
            direction = -1 * direction;
            start = start + 2 * direction;
            frequency = arrFrequency[start];
        }
        // 改变索引，下一次hover时候使用
        start = start + direction;
        // 创建一个OscillatorNode, 它表示一个周期性波形（振荡），基本上来说创造了一个音调
        var oscillator = audioCtx.createOscillator();
        // 创建一个GainNode,它可以控制音频的总音量
        var gainNode = audioCtx.createGain();
        // 把音量，音调和终节点进行关联
        oscillator.connect(gainNode);
        // audioCtx.destination返回AudioDestinationNode对象，表示当前audio context中所有节点的最终节点，一般表示音频渲染设备
        gainNode.connect(audioCtx.destination);
        // 指定音调的类型，其他还有square|triangle|sawtooth
        oscillator.type = 'sine';
        // 设置当前播放声音的频率，也就是最终播放声音的调调
        oscillator.frequency.value = frequency;
        // 当前时间设置音量为0
        gainNode.gain.setValueAtTime(0, audioCtx.currentTime);
        // 0.01秒后音量为1
        gainNode.gain.linearRampToValueAtTime(1, audioCtx.currentTime + 0.01);
        // 音调从当前时间开始播放
        oscillator.start(audioCtx.currentTime);
        // 1秒内声音慢慢降低，是个不错的停止声音的方法
        gainNode.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 1);
        // 1秒后完全停止声音
        oscillator.stop(audioCtx.currentTime + 1);
    }
    // ---START
    // 创建新的音频上下文接口
    var audioCtx = new AudioContext();
    // 发出的声音频率数据，表现为音调的高低
    var arrFrequency = [196.00, 220.00, 246.94, 261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 493.88, 523.25, 587.33, 659.25, 698.46, 783.99, 880.00, 987.77, 1046.50];

    // 音调依次递增或者递减处理需要的参数
    var start = 0, direction = 1;
    // ---END

</script>

</html>